﻿using System;
using FastClone;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace FastCloneTest
{
    [TestClass]
    public class ClonerTest
    {
        [TestMethod]
        public void CloneCircularReferencesTest()
        {
            Parent parent = new Parent();
            Child child = new Child();

            parent.Self = parent;
            parent.Child = child;

            child.Parent = parent;

            Parent target = Cloner.Clone(parent);
            Assert.AreSame(target, target.Self);
            Assert.AreSame(target, target.Child.Parent);
        }

        [TestMethod]
        public void CloneInheritanceTest()
        {
            InheritedClass inheritedClass = new InheritedClass("42");
            InheritedClass target = Cloner.Clone(inheritedClass);

            Assert.AreEqual("42", target.Name);
        }

        [TestMethod]
        public void CloneMultiCloneSameReference()
        {
            Child child = new Child();
            Parent a = new Parent();
            Parent b = new Parent();

            a.Child = child;
            b.Child2 = child;

            Parent testA = Cloner.Clone(a);
            Parent testB = Cloner.Clone(b);

            Assert.AreNotSame(testA, testB);
            Assert.AreNotSame(testA.Child, testB.Child2);
        }

        [TestMethod]
        public void CloneReadonlyTest()
        {
            ReadOnly readOnly = new ReadOnly("42");
            ReadOnly target = Cloner.Clone(readOnly);

            Assert.AreEqual("42", readOnly.Name);
            Assert.AreEqual("42", target.Name);
        }

        [TestMethod]
        public void CloneSimpleValueTest()
        {
            SimpleValues original = new SimpleValues(42, new[] { 43, 44 }, "45", new[] { "46", "47" })
            { Int = 48, IntArray = new[] { 49, 50 }, String = "51", StringArray = new[] { "52", "53" } };

            SimpleValues clone = Cloner.Clone(original);

            Assert.AreEqual(original.PrivateInt, clone.PrivateInt);
            Assert.AreEqual(original.PrivateString, clone.PrivateString);
            Assert.AreEqual(original.PrivateIntArray[0], clone.PrivateIntArray[0]);
            Assert.AreEqual(original.PrivateIntArray[1], clone.PrivateIntArray[1]);
            Assert.AreEqual(original.PrivateStringArray[0], clone.PrivateStringArray[0]);
            Assert.AreEqual(original.PrivateStringArray[1], clone.PrivateStringArray[1]);
            Assert.AreEqual(original.Int, clone.Int);
            Assert.AreEqual(original.String, clone.String);
            Assert.AreEqual(original.IntArray[0], clone.IntArray[0]);
            Assert.AreEqual(original.IntArray[1], clone.IntArray[1]);
            Assert.AreEqual(original.StringArray[0], clone.StringArray[0]);
            Assert.AreEqual(original.StringArray[1], clone.StringArray[1]);
        }

        [TestMethod]
        public void CloneComplexValueTest()
        {
            Child arrayChild = new Child();
            ComplexValues original = new ComplexValues(new Child(), new Child(), new[] { arrayChild, arrayChild, new Child() })
            {
                Same1 = new Child(), Different = new Child(), Array = new[] { arrayChild, arrayChild, new Child() }
            };
            original.Same2 = original.Same1;

            ComplexValues clone = Cloner.Clone(original);

            Assert.AreNotSame(original.PrivateSame1, clone.PrivateSame1);
            Assert.AreNotSame(original.PrivateSame2, clone.PrivateSame2);
            Assert.AreSame(clone.PrivateSame1, clone.PrivateSame2);
            Assert.AreNotSame(clone.PrivateSame1, clone.PrivateDifferent);

            Assert.AreSame(clone.PrivateArray[0], clone.PrivateArray[1]);
            Assert.AreNotSame(clone.PrivateArray[0], clone.PrivateArray[2]);
            Assert.AreNotSame(original.PrivateArray[0], clone.PrivateArray[0]);

            Assert.AreNotSame(original.Same1, clone.Same1);
            Assert.AreNotSame(original.Same2, clone.Same2);
            Assert.AreSame(clone.Same1, clone.Same2);
            Assert.AreNotSame(clone.Same1, clone.Different);

            Assert.AreSame(clone.Array[0], clone.Array[1]);
            Assert.AreNotSame(clone.Array[0], clone.Array[2]);
            Assert.AreNotSame(original.Array[0], clone.Array[0]);
        }

        [TestMethod]
        public void CloneTestSimpleValue()
        {
            int original = 42;
            int clone = Cloner.Clone(original);

            Assert.AreEqual(original, clone);
        }

        [TestMethod]
        public void CloneSimpleArrayTest()
        {
            int[] original = { 42, 43 };
            int[] clone = Cloner.Clone(original);

            Assert.AreNotSame(original, clone);
            Assert.AreEqual(original[0], clone[0]);
            Assert.AreEqual(original[1], clone[1]);
        }

        [TestMethod]
        public void CloneComplexArrayTest()
        {
            Child child = new Child();
            Child[] original = { child, child, new Child() };
            Child[] clone = Cloner.Clone(original);

            Assert.AreNotSame(original, clone);
            Assert.AreNotSame(original[0], clone[0]);
            Assert.AreNotSame(original[1], clone[1]);
            Assert.AreNotSame(original[2], clone[2]);

            Assert.AreSame(clone[0], clone[1]);
            Assert.AreNotSame(clone[0], clone[2]);
        }

        [TestMethod]
        public void CloneNestedArrayTest()
        {
            int[][] original = { new[] { 42, 43 }, new[] { 44, 45 } };
            int[][] clone = Cloner.Clone(original);

            Assert.AreNotSame(original, clone);
            Assert.AreNotSame(original[0], clone[0]);
            Assert.AreNotSame(original[1], clone[1]);

            Assert.AreEqual(original[0][0], clone[0][0]);
            Assert.AreEqual(original[0][1], clone[0][1]);
            Assert.AreEqual(original[1][0], clone[1][0]);
            Assert.AreEqual(original[1][1], clone[1][1]);
        }

        [TestMethod]
        public void CloneNestedComplexArrayTest()
        {
            TestClass[][] original = { new[] { new TestClass { Id = Guid.NewGuid() }, new TestClass { Id = Guid.NewGuid() } }, new[] { new TestClass { Id = Guid.NewGuid() }, new TestClass { Id = Guid.NewGuid() } } };
            TestClass[][] clone = Cloner.Clone(original);

            Assert.AreNotSame(original, clone);
            Assert.AreNotSame(original[0], clone[0]);
            Assert.AreNotSame(original[1], clone[1]);

            Assert.AreNotSame(original[0][0], clone[0][0]);
            Assert.AreNotSame(original[0][1], clone[0][1]);
            Assert.AreNotSame(original[1][0], clone[1][0]);
            Assert.AreNotSame(original[1][1], clone[1][1]);
        }

        [TestMethod]
        public void CloneStructTest()
        {
            Struct original = new Struct { Value = 42 };
            Struct clone = Cloner.Clone(original);

            Assert.AreEqual(original.Value, clone.Value);
        }

        [TestMethod]
        public void CloneStructArrayTest()
        {
            Struct[] original = { new Struct { Value = 42 }, new Struct { Value = 42 } };
            Struct[] clone = Cloner.Clone(original);

            Assert.AreNotSame(original, clone);
            Assert.AreEqual(original[0].Value, clone[0].Value);
            Assert.AreEqual(original[1].Value, clone[1].Value);
        }

        [TestMethod]
        public void CloneNullableIntTest()
        {
            NullableInt original = new NullableInt { Value = 42 };
            NullableInt clone = Cloner.Clone(original);

            Assert.AreNotSame(original, clone);
            Assert.AreEqual(original.Value, clone.Value);

            original = new NullableInt();
            clone = Cloner.Clone(original);

            Assert.IsNull(clone.Value);
        }

        #region Nested type: BaseClass

        class BaseClass
        {
            readonly string _Name;
            protected BaseClass() { }

            protected BaseClass(string name) { _Name = name; }
            public string Name { get { return _Name; } }
        }

        #endregion

        #region Nested type: Child

        class Child
        {
            public Parent Parent { get; set; }
        }

        #endregion

        #region Nested type: ComplexValues

        class ComplexValues
        {
            readonly Child[] _PrivateArray;
            readonly Child _PrivateDifferent;
            readonly Child _PrivateSame1;
            readonly Child _PrivateSame2;

            public ComplexValues(Child privateSame, Child privateDifferent, Child[] privateArray)
            {
                _PrivateSame1 = privateSame;
                _PrivateSame2 = privateSame;
                _PrivateDifferent = privateDifferent;
                _PrivateArray = privateArray;
            }

            internal Child PrivateSame1 { get { return _PrivateSame1; } }
            internal Child PrivateSame2 { get { return _PrivateSame2; } }
            internal Child PrivateDifferent { get { return _PrivateDifferent; } }
            internal Child[] PrivateArray { get { return _PrivateArray; } }

            public Child Same1 { get; set; }
            public Child Same2 { get; set; }
            public Child Different { get; set; }
            public Child[] Array { get; set; }
        }

        #endregion

        #region Nested type: InheritedClass

        class InheritedClass : BaseClass
        {
            public InheritedClass(string name) : base(name) { }
        }

        #endregion

        #region Nested type: NullableInt

        class NullableInt
        {
            public int? Value { get; set; }
        }

        #endregion

        #region Nested type: Parent

        class Parent
        {
            public Parent Self { get; set; }
            public Child Child { get; set; }
            public Child Child2 { get; set; }
        }

        #endregion

        #region Nested type: ReadOnly

        class ReadOnly
        {
            readonly string _Name;

            public ReadOnly(string name) { _Name = name; }

            public string Name { get { return _Name; } }
        }

        #endregion

        #region Nested type: SimpleValues

        class SimpleValues
        {
            readonly int _PrivateInt;
            readonly int[] _PrivateIntArray;
            readonly string _PrivateString;
            readonly string[] _PrivateStringArray;

            public SimpleValues(int privateInt, int[] privateIntArray, string privateString, string[] privateStringArray)
            {
                _PrivateInt = privateInt;
                _PrivateIntArray = privateIntArray;
                _PrivateString = privateString;
                _PrivateStringArray = privateStringArray;
            }

            public string String { get; set; }
            public int Int { get; set; }
            public int[] IntArray { get; set; }
            public string[] StringArray { get; set; }

            internal int PrivateInt { get { return _PrivateInt; } }
            internal string PrivateString { get { return _PrivateString; } }
            internal int[] PrivateIntArray { get { return _PrivateIntArray; } }
            internal string[] PrivateStringArray { get { return _PrivateStringArray; } }
        }

        #endregion

        #region Nested type: Struct

        internal struct Struct
        {
            internal int Value;
        }

        #endregion

        #region Nested type: TestClass

        public class TestClass
        {
            public Guid Id { get; set; }
        }

        #endregion
    }
}